import {Piece} from './piece'
import VisiblePieces from '../constants/visible-pieces'
import {EventType, PieceState, PlayerColor} from './types'
import {Player} from './player'
import {BehaviorSubject} from 'rxjs'
import {BoardCenter, BoardTotal} from '../constants/constants'

export class Board {
  pieces: Piece[][] = []
  playerRed = new Player('Red')
  playerBlue = new Player('Blue')
  $boardEvent = new BehaviorSubject({})
  private currentPlayer = this.playerRed

  constructor() {
    this.initPieces()
  }

  reset() {
    this.pieces.forEach(row => row.forEach(p => {
      p.color = 'Unknown'
      p.state = 'Unactivated'
    }))
    this.playerBlue.reset()
    this.playerRed.reset()
    this.currentPlayer = this.playerRed
    this.triggerEvent('AiMove')
  }

  start() {
    this.setBatchState('Marked', 'Deleted', true)
    this.currentPlayer = this.playerBlue
    this.triggerEvent('AiMove')
  }

  active(piece: Piece) {
    if (this.currentPlayer.doMarking) {
      return
    }
    piece.color = this.currentPlayer.color
    piece.state = 'Activated'
    this.currentPlayer.move(piece, 'active')
    if (this.canSay3(piece, this.pieces)) {
      this.setMarking()
      return
    }
    if (this.readyToStart()) {
      this.triggerEvent('Starting')
    } else {
      this.switchPlayer()
    }
  }

  setMarking() {
    this.pieces.forEach(row => {
      row.forEach(p => {
        if (p.visible && p.color !== this.currentPlayer.color && p.state === 'Activated') {
          p.state = 'Marking'
        }
      })
    })
    this.currentPlayer.doMarking = true
    this.triggerEvent('AiMarking')
  }

  setDeleting() {
    this.pieces.forEach(row => {
      row.forEach(p => {
        if (p.visible && p.color !== this.currentPlayer.color && p.state === 'Activated') {
          p.state = 'Deleting'
        }
      })
    })
    this.currentPlayer.doDeleting = true
    this.triggerEvent('AiDeleting')
  }

  moveOut(piece: Piece) {
    if (piece.color !== this.currentPlayer.color) {
      console.log('Not your piece')
      return
    }
    if (this.currentPlayer.doMoving) {
      console.log('Cannot active again')
      return
    }
    piece.state = 'MovingOut'
    piece.neighbors
      .map(x => this.pieces[x[1]][x[0]])
      .filter(x => x.state === 'Deleted')
      .forEach(x => x.state = 'MovingIn')
    this.currentPlayer.doMoving = true
    this.triggerEvent('AiMoving')
  }

  cancelMoveOut(piece: Piece) {
    piece.state = 'Activated'
    piece.neighbors
      .map(x => this.pieces[x[1]][x[0]])
      .filter(x => x.state === 'MovingIn')
      .forEach(x => x.state = 'Deleted')
    this.currentPlayer.doMoving = false
  }


  moveIn(piece: Piece) {
    piece.state = 'Activated'
    piece.color = this.currentPlayer.color
    this.currentPlayer.move(piece, 'move')

    const outPiece = piece.neighbors
      .map(x => this.pieces[x[1]][x[0]])
      .find(x => x.state === 'MovingOut')
    outPiece.state = 'Deleted'
    outPiece.color = 'Unknown'
    this.currentPlayer.doMoving = false
    this.setBatchState('MovingIn', 'Deleted')
    if (this.canSay3(piece, this.pieces)) {
      this.setDeleting()
    } else if (!this.canMove(this.currentPlayer.opponentColor)) {
      this.triggerEvent('Finished')
    } else {
      this.switchPlayer()
    }
  }

  mark(piece: Piece) {
    piece.state = 'Marked'
    this.setBatchState('Marking', 'Activated')
    this.currentPlayer.doMarking = false
    if (this.readyToStart()) {
      this.triggerEvent('Starting')
    } else {
      this.switchPlayer()
    }
  }

  kill(piece: Piece) {
    piece.state = 'Deleted'
    piece.color = 'Unknown'
    this.setBatchState('Deleting', 'Activated')
    this.currentPlayer.doDeleting = false
    if (this.win() || !this.canMove(this.currentPlayer.opponentColor)) {
      this.triggerEvent('Finished')
    } else {
      this.switchPlayer()
    }
  }

  private setBatchState(from: PieceState, to: PieceState, clearColor = false) {
    this.pieces.forEach(row => {
      row.forEach(p => {
        if (p.state === from) {
          p.state = to
          if (clearColor) {
            p.color = 'Unknown'
          }
        }
      })
    })
  }

  private initPieces() {
    for (let row = 0; row <= BoardTotal; row++) {
      const rowPieces = []
      for (let col = 0; col <= BoardTotal; col++) {
        const visiblePiece = VisiblePieces[row].find(x => x.y === col)
        let group = ''
        if (visiblePiece) {
          if (row < BoardCenter && col < BoardCenter) {
            group = 'top-left'
          } else if (row < BoardCenter && col > BoardCenter) {
            group = 'top-right'
          } else if (row > BoardCenter && col < BoardCenter) {
            group = 'bottom-left'
          } else if (row > BoardCenter && col > BoardCenter) {
            group = 'bottom-right'
          }
        }
        rowPieces.push(new Piece(!!visiblePiece, col, row, group, visiblePiece ? visiblePiece.neighbors : null))
      }
      this.pieces.push(rowPieces)
    }
  }

  private switchPlayer() {
    this.currentPlayer = this.currentPlayer === this.playerRed ? this.playerBlue : this.playerRed
    this.triggerEvent('AiMove')
  }

  private canSay3(piece: Piece, pieces: Piece[][]) {
    return this.compareRow(piece, pieces) || this.compareColumn(piece, pieces) || this.compareOblique(piece, pieces)
  }

  private canMove(color: PlayerColor) {
    return this.pieces.some(row => {
      return row.some(p => p.visible && p.color === color && p.state === 'Activated'
        && p.neighbors.some(i => this.pieces[i[1]][i[0]].state === 'Deleted'))
    })
  }

  private compareRow(piece: Piece, pieces: Piece[][]): boolean {
    return pieces[piece.rowIndex]
      .filter(x => piece.rowIndex !== BoardCenter ||
        (x.colIndex > BoardCenter && piece.colIndex > BoardCenter) ||
        (x.colIndex < BoardCenter && piece.colIndex < BoardCenter))
      .filter(x => x.state === 'Activated' && Board.compare(x, piece)).length === 3
  }

  private compareColumn(piece: Piece, pieces: Piece[][]): boolean {
    return pieces.filter(row => row.some(x => {
      return (piece.colIndex !== BoardCenter ||
        (x.rowIndex > BoardCenter && piece.rowIndex > BoardCenter) ||
        (x.rowIndex < BoardCenter && piece.rowIndex < BoardCenter))
        && x.colIndex === piece.colIndex && x.state === 'Activated' && Board.compare(x, piece)
    })).length >= 3
  }

  private compareOblique(piece: Piece, pieces: Piece[][]): boolean {
    return piece.obliqueGroup && pieces.filter(row => {
      return row.some(x => x.obliqueGroup === piece.obliqueGroup && x.state === 'Activated' && x.color === piece.color)
    }).length >= 3
  }

  private static compare(target: Piece, source: Piece): boolean {
    return target.state === 'Activated' && target.color === source.color && target.visible
  }

  private readyToStart() {
    return !this.pieces.some(row => {
      return row.some(x => x.visible && x.state === 'Unactivated')
    })
  }

  private win() {
    let count = 0
    this.pieces.forEach(row => {
      count += row.filter(p => p.visible && p.color !== this.currentPlayer.color && p.state === 'Activated').length
    })
    return count < 3
  }

  private triggerEvent(type: EventType) {
    this.$boardEvent.next({type, player: this.currentPlayer})
  }
}